package cn.fufeii.generator.service;

import cn.fufeii.generator.config.ConfigHolder;
import cn.fufeii.generator.config.GeneratorConfig;
import cn.fufeii.generator.domain.ColumnDO;
import cn.fufeii.generator.domain.TableDO;
import cn.fufeii.generator.util.TypeUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.ClasspathResourceLoader;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 抽象各层方法
 * 提供通用方法
 *
 * @author FuFei
 * @date 2021/6/26
 */
public abstract class AbstractGenerator implements Generator {

    private final GroupTemplate groupTemplate;

    public AbstractGenerator() {
        //初始化代码
        ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader();
        Configuration cfg;
        try {
            cfg = Configuration.defaultConfiguration();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        this.groupTemplate = new GroupTemplate(resourceLoader, cfg);
    }

    /**
     * 获取驼峰命名下的实体名字
     */
    protected final String getEntityName(String originTableName) {
        // 原始数据库表明转为java实体名，例如 ab_cde_fgh AB_CDE_FGH ==> AbCdeFgh
        originTableName = originTableName.replaceFirst(ConfigHolder.getRuleEntity().getTablePrefix(), "").toLowerCase();
        return StrUtil.upperFirst(StrUtil.toCamelCase(originTableName));
    }

    /**
     * 创建/获取文件Writer
     */
    protected final BufferedWriter getFileWriter(String moduleName, String fileName) {
        return FileUtil.getWriter(FileUtil.file(ConfigHolder.getRuleCommon().getOutputPath() + moduleName + "\\" + fileName), StandardCharsets.UTF_8, false);
    }

    /**
     * 获取实体对象的字段类型和导包
     */
    protected final Map<String, Object> getFieldAndImport(List<ColumnDO> columnDOList) {
        // 实体类的字段名字段类型以及需要的导包
        List<ColumnDO> fieldList = CollUtil.newArrayList();
        Set<String> importList = CollUtil.newHashSet();
        for (ColumnDO columnDO : columnDOList) {
            ColumnDO column = new ColumnDO();
            column.setJavaName(StrUtil.lowerFirst(StrUtil.toCamelCase(columnDO.getName())));
            column.setName(columnDO.getName());
            column.setComment(StrUtil.isBlank(columnDO.getComment()) ? columnDO.getName() : columnDO.getComment());
            String javaType = TypeUtil.getJavaType(columnDO.getType());
            String anImport = TypeUtil.getImport(javaType);
            if (StrUtil.isNotBlank(anImport)) {
                importList.add(anImport);
            }
            column.setType(javaType);
            fieldList.add(column);
        }

        Map<String, Object> ret = CollUtil.newHashMap();
        ret.put("fieldList", fieldList);
        ret.put("importList", importList);
        return ret;
    }

    protected final void setCommonVariable(Map<String, Object> variableMap, TableDO tableDO) {
        variableMap.put("author", ConfigHolder.getRuleCommon().getAuthor());
        variableMap.put("date", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));

        variableMap.put("packagePath", ConfigHolder.getRuleCommon().getPackagePath());
        String entityName = this.getEntityName(tableDO.getName());
        variableMap.put("entityName", entityName);
        variableMap.put("lowerEntityName", StrUtil.lowerFirst(entityName));
        variableMap.put("tableComment", StrUtil.isBlank(tableDO.getComment()) ? tableDO.getName() : tableDO.getComment());
    }

    protected final Map<String, Object> getEntityCommonVariableMap(TableDO tableDO) {
        GeneratorConfig.Rule.Entity ruleEntity = ConfigHolder.getRuleEntity();
        Map<String, Object> variableMap = this.getFieldAndImport(tableDO.getColumnDOList());
        variableMap.put("useLombok", ruleEntity.getUseLombok());
        variableMap.put("tableName", tableDO.getName());
        variableMap.put("idName", ruleEntity.getIdName());

        // 乐观锁
        variableMap.put("useVersionLock", ruleEntity.getUesVersionLock());
        String versionLockField = ruleEntity.getVersionLockField();
        variableMap.put("versionLockField", versionLockField);
        boolean matchVersion = Convert.convert(new TypeReference<List<ColumnDO>>() {
        }, variableMap.get("fieldList")).stream().map(ColumnDO::getName).anyMatch(versionLockField::equals);
        variableMap.put("containsVersionLockField", matchVersion);

        // 审计
        variableMap.put("uesAudit", ruleEntity.getUesAudit());
        String auditCreateDateTimeField = ruleEntity.getAuditCreateDateTimeField();
        String auditUpdateDateTimeField = ruleEntity.getAuditUpdateDateTimeField();
        variableMap.put("auditCreateDateTimeField", auditCreateDateTimeField);
        variableMap.put("auditUpdateDateTimeField", auditUpdateDateTimeField);
        boolean matchAuditCreate = Convert.convert(new TypeReference<List<ColumnDO>>() {
        }, variableMap.get("fieldList")).stream().map(ColumnDO::getName).anyMatch(it -> it.equals(auditCreateDateTimeField));
        boolean matchAuditUpdate = Convert.convert(new TypeReference<List<ColumnDO>>() {
        }, variableMap.get("fieldList")).stream().map(ColumnDO::getName).anyMatch(it -> it.equals(auditUpdateDateTimeField));
        variableMap.put("containsAuditCreateDateTimeField", matchAuditCreate);
        variableMap.put("containsAuditUpdateDateTimeField", matchAuditUpdate);

        this.setCommonVariable(variableMap, tableDO);
        return variableMap;
    }

    protected final void render(String tplPath, String moduleName, String fileName, Map<String, Object> otherVariable) {
        Template template = groupTemplate.getTemplate(tplPath);
        if (CollUtil.isNotEmpty(otherVariable)) {
            template.binding(otherVariable);
        }
        template.renderTo(this.getFileWriter(moduleName, fileName));
    }


    /**
     * 生成VO对象
     */
    private void generateEntityVO(TableDO tableDO) {
        Map<String, Object> entityCommonVariableMap = this.getEntityCommonVariableMap(tableDO);
        entityCommonVariableMap.put("dtoUseSwagger", ConfigHolder.getRuleEntity().getDtoUseSwagger());
        this.render("template/common/entityReq.btl", "vo/req", this.getEntityName(tableDO.getName()) + "Req.java", entityCommonVariableMap);
        this.render("template/common/entityResp.btl", "vo/resp", this.getEntityName(tableDO.getName()) + "Resp.java", entityCommonVariableMap);
    }


    protected abstract void generateEntity(TableDO tableDO);

    protected abstract void generateController(TableDO tableDO);

    protected abstract void generateService(TableDO tableDO);

    protected abstract void generateOrm(TableDO tableDO);

    @Override
    public final void generate(TableDO tableDO) {
        if (ConfigHolder.getRuleEntity().getEnable()) {
            StaticLog.info("===================== start   entity   =====================");
            this.generateEntity(tableDO);
            StaticLog.info("===================== end     entity   =====================\n");

            StaticLog.info("===================== start   entityVO =====================");
            this.generateEntityVO(tableDO);
            StaticLog.info("===================== end     entityVO =====================\n");
        }

        if (ConfigHolder.getRuleController().getEnable()) {
            StaticLog.info("===================== start controller =====================");
            this.generateController(tableDO);
            StaticLog.info("===================== end   controller =====================\n");
        }

        if (ConfigHolder.getRuleService().getEnable()) {
            StaticLog.info("===================== start    service =====================");
            this.generateService(tableDO);
            StaticLog.info("===================== end      service =====================\n");
        }

        if (ConfigHolder.getRuleOrm().getEnable()) {
            StaticLog.info("===================== start        orm =====================");
            this.generateOrm(tableDO);
            StaticLog.info("===================== end          orm =====================\n");
        }
    }


}